﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using Microsoft.VisualStudio.IntelliTrace;
using System.Globalization;
using System.IO;

namespace Microsoft.VisualStudio.Samples
{
    public class ChainConsoleWriter
    {
        protected IntelliTraceProcess Process { set; get; }
        protected Writer Writer { get; set; }

        #region support methods

        protected DateTime GetDateTimeFromEventTime(IntelliTraceProcess process, long eventTime)
        {
            DateTime eventDateTime = DateTime.FromFileTime(process.SystemInformationEvent.StartTime);
            double relativeStart = ((eventTime - process.SystemInformationEvent.TimerStartTime) * 1000) / process.SystemInformationEvent.TimerFrequency;
            eventDateTime = eventDateTime.AddMilliseconds(relativeStart);
            return eventDateTime;
        }

        protected void DumpProperties(NameValueCollection properties)
        {
            Writer.WriteLine(s_nameValueFormat, "Name", "Value");
            Writer.WriteLine(s_nameValueFormat, "----", "-----");
            foreach (string name in properties.AllKeys)
            {
                Writer.WriteLine(s_nameValueFormat, name, properties[name]);
            }
        }

        protected void DumpDataElements(IList<IDataElement> elements, int nestingLevel)
        {
            if (elements.Count > 0)
            {
                if (nestingLevel == 0)
                {
                    Writer.WriteLine(s_parameterFormat, "", "Name", "Value", "Type");
                }
                foreach (IDataElement element in elements)
                {
                    Writer.WriteLine(s_parameterFormat, new string(' ', nestingLevel * 2), element.Name, element.Value, element.TypeName);
                    if (element.HasChildren)
                    {
                        DumpDataElements(element.GetChildren(), nestingLevel + 1);
                    }
                }
            }
        }

        protected void DumpStack(IntelliTraceProcess process, IList<IntelliTraceStackFrame> stackFrames)
        {
            Writer.WriteLine(s_stackFormat, "Method", "IL offset", "Module", "Ver");
            foreach (IntelliTraceStackFrame sf in stackFrames)
            {
                ResolvedStackFrame stackFrame = new ResolvedStackFrame(process, sf);
                Writer.WriteLine(s_stackFormat,
                    FormatShortMethodName(stackFrame.Method),
                    "0x" + stackFrame.StackFrame.ILOffset.ToString("x8", CultureInfo.CurrentCulture),
                    (stackFrame.Module == null) ? "<unknown>" : Path.GetFileName(stackFrame.Module.FileName),
                    stackFrame.StackFrame.MethodVersion);
                if (stackFrame.SourceLocation != null)
                {
                    Writer.WriteLine(FormatSourceLocation(stackFrame.SourceLocation));
                }
            }
        }

        protected IntelliTraceSourceLocation GetSourceLocation(int intelliTraceMethodToken, int ilOffset)
        {
            // To get the method location, translate the IntelliTrace method
            // token to an mvid, metadata token pair, and the get the source
            // location for the method + offset from the module.
            Guid mvid;
            int metadataToken;
            Process.GetMetadataToken(intelliTraceMethodToken, out mvid, out metadataToken);
            return Process.GetModule(mvid).GetSourceLocation(metadataToken, ilOffset);
        }

        protected static string FormatBlob(IList<byte> bytes)
        {
            StringBuilder blob = new StringBuilder();
            foreach (byte b in bytes)
            {
                blob.Append(b.ToString("x2", CultureInfo.CurrentCulture));
                blob.Append(' ');
            }
            return blob.ToString();
        }

        protected static string FormatSeparator(IntelliTraceEvent ev)
        {
            string eventFullTypeName = ev.GetType().ToString();
            int afterLastDot = eventFullTypeName.LastIndexOf('.') + 1;
            string eventTypeName;
            if (afterLastDot > 0 && afterLastDot < eventFullTypeName.Length)
            {
                eventTypeName = eventFullTypeName.Substring(afterLastDot);
            }
            else
            {
                eventTypeName = eventFullTypeName;
            }

            StringBuilder separator = new StringBuilder(string.Format(CultureInfo.CurrentCulture, "-- {0} ", eventTypeName));
            separator.Append('-', 66 - eventTypeName.Length);
            return separator.ToString();
        }

        protected static string FormatShortMethodName(ResolvedMethod method)
        {
            // Format the method name into the form:
            // type.method-name

            StringBuilder methodName = new StringBuilder();
            methodName.Append(method.ContainingTypeName);
            methodName.Append(".");
            methodName.Append(method.MethodName);
            return methodName.ToString();
        }

        protected static string FormatFullMethodName(ResolvedMethod method)
        {
            // Format the method name into the form:
            // type.method-name(param1-type,param2-type):return-type

            StringBuilder methodName = new StringBuilder();
            methodName.Append(method.ContainingTypeName);
            methodName.Append(".");
            methodName.Append(method.MethodName);
            methodName.Append("(");
            IList<string> parameterTypeNames = method.ParameterTypeNames;
            for (int i = 0; i < parameterTypeNames.Count; i++)
            {
                string parameterTypeName = parameterTypeNames[i];
                methodName.Append(parameterTypeName);
                if (i < parameterTypeNames.Count - 1)
                {
                    methodName.Append(",");
                }
            }
            methodName.Append("):");
            methodName.Append(method.ReturnTypeName);

            return methodName.ToString();
        }

        protected static string FormatSourceLocation(IntelliTraceSourceLocation sourceLocation)
        {
            if (sourceLocation == null)
            {
                return "<no location available>";
            }
            return string.Format(CultureInfo.CurrentCulture, s_stackSourceLocationFormat,
                sourceLocation.FileName,
                sourceLocation.StartLine,
                sourceLocation.StartColumn,
                sourceLocation.EndLine,
                sourceLocation.EndColumn);
        }
        #endregion

        #region Protected data

        protected const string s_separator = "----------------------------------------------------------------------";
        protected const string s_nameValueFormat = "{0,-30} {1}";
        protected const string s_stackFormat = "  {0,-50} {1,-10} {2,-15} {3}";
        protected const string s_stackSourceLocationFormat = "    {0,-48} {1},{2} - {3},{4}";
        protected const string s_parameterFormat = "  {0}{1,-25} {2,-25} {3}";

        #endregion

    }
}
